package com.kehutong.payment.service;

import com.kehutong.common.DoveClient;
import com.kehutong.common.Java8TimeUtils;
import com.kehutong.common.syn.CallBackReturn;
import com.kehutong.common.syn.Locks;
import com.kehutong.common.urlconfig.LogicUrlConfig;
import com.kehutong.common.urlconfig.PayUrlConfig;
import com.kehutong.payment.entity.ThirdOrder;
import com.kehutong.payment.enums.PayTypeEnum;
import com.kehutong.payment.enums.SignType;
import com.kehutong.payment.util.*;
import com.kehutong.payment.vo.Merchant;
import org.coraframework.inject.Inject;
import org.coraframework.json.JSONObject;
import org.coraframework.logger.Logger;
import org.coraframework.logger.LoggerFactory;
import org.coraframework.mvc.http.result.Result;
import org.coraframework.orm.Pool;
import org.coraframework.orm.jdbc.JdbcSession;
import org.coraframework.orm.jdbc.execute.entity.FindOne;
import org.coraframework.util.Objects;
import org.coraframework.util.secret.MD5;
import org.coraframework.util.time.TimeUtil;

import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

public class WxpayService {

    protected Logger logger = LoggerFactory.getLogger(WxpayService.class);

    @Inject
    private JdbcSession jdbcSession;
    @Inject
    private LogicUrlConfig logicConfig;
    @Inject
    private PayUrlConfig payConfig;
    @Inject
    private DataUtil dataUtil;
    @Inject
    private TencentSignature signature;
    @Inject
    private DoveClient doveClient;
    @Inject
    private RouteService routeService;

    public Object callback(JSONObject jsonObject) throws Exception {
        String orderSn = jsonObject.getString("out_trade_no");

        if (!TencentPayResult.isOk(jsonObject, "return_code", "result_code")) {
            return Result.text(dataUtil.getMessage(TencentPayResult.FAIL, "BUINESS FAIL"));
        }

        CallBackReturn<String> result = ()->{
            String sub_mch_id = jsonObject.getString("sub_mch_id");
            String transaction_id = jsonObject.getString("transaction_id");
            ThirdOrder order = getThirdOrder(sub_mch_id, orderSn, null, transaction_id);
            if (Objects.isNull(order)) {
                return dataUtil.getMessage(TencentPayResult.FAIL, "ERROR DATA FAIL");
            }
            Merchant merchant = routeService.route(order.getCompanyNo(), order.getCommunityNo());
            if (!signature.checkIsSignValidFromResponseString(jsonObject, merchant.getMchSecret())) {
                return dataUtil.getMessage(TencentPayResult.FAIL, "SIGN CHECK FAIL");
            }
            // 避免重复通知
            if (!order.isPaid()) {
                int sendCount = order.getSendCount();
                if (sendCount < 15) { // 默认通知15次，通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h
                    setPaidOrder(order, jsonObject);
                    order.setSendCount(++sendCount);

                    logger.info("========WxpayService.callback回调：companyNo={},参数：{}==========", order.getCompanyNo(), jsonObject.toJSONString());

                    String callbackResult = doveClient.postString(order.getCallbackUrl(), http -> {
                        http.addHeader("companyNo", order.getCompanyNo());
                        jsonObject.put("subOrderNo", order.getSubOrderNo());
                        http.setBody(jsonObject.toJSONString());
                    });

                    if ("SUCCESS".equalsIgnoreCase(callbackResult)) {
                        jdbcSession.updateById(order);
                        return dataUtil.getMessage(TencentPayResult.SUCCESS, "OK");
                    }
                    jdbcSession.updateById(order);
                    return dataUtil.getMessage(TencentPayResult.SUCCESS, "OK");
                }
                return dataUtil.getMessage(TencentPayResult.FAIL, "FAIL");
            } else {
                return dataUtil.getMessage(TencentPayResult.SUCCESS, "OK");
            }
        };

        String resultText = (String) Locks.exeSyn(orderSn, result);
        if (Objects.isEmpty(resultText)) {
            resultText = dataUtil.getMessage(TencentPayResult.FAIL, "FAIL");
            return Result.text(resultText);
        }
        // 默认失败，使用微信的通知次数
        return Result.text(result);
    }

    public Object v2Callback(JSONObject jsonObject) throws Exception {
        String orderSn = jsonObject.getString("out_trade_no");

        if (!TencentPayResult.isOk(jsonObject, "return_code", "result_code")) {
            return Result.text(dataUtil.getMessage(TencentPayResult.FAIL, "BUINESS FAIL"));
        }

        CallBackReturn<String> result = ()->{
            String sub_mch_id = jsonObject.getString("sub_mch_id");
            String transaction_id = jsonObject.getString("transaction_id");
            ThirdOrder order = getThirdOrder(sub_mch_id, orderSn, null, transaction_id);
            if (Objects.isNull(order)) {
                return dataUtil.getMessage(TencentPayResult.FAIL, "ERROR DATA FAIL");
            }
            Merchant merchant = routeService.routeV2(order.getCompanyNo(), order.getCommunityNo());
            if (!signature.checkIsSignValidFromResponseString(jsonObject, merchant.getMchSecret())) {
                return dataUtil.getMessage(TencentPayResult.FAIL, "SIGN CHECK FAIL");
            }
            // 避免重复通知
            if (!order.isPaid()) {
                int sendCount = order.getSendCount();
                if (sendCount < 15) { // 默认通知15次，通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h
                    setV2PaidOrder(order, jsonObject);
                    order.setSendCount(++sendCount);

                    logger.info("========WxpayService.v2Callback回调：companyNo={},参数：{}==========", order.getCompanyNo(), jsonObject.toJSONString());

                    JSONObject callbackResult = doveClient.post(order.getCallbackUrl(), http -> {
                        http.addHeader("companyNo", order.getCompanyNo());
                        jsonObject.put("subOrderNo", order.getSubOrderNo());
                        http.setBody(jsonObject.toJSONString());
                    });

                    if (callbackResult.getIntValue("code") == 200) {
                        order.setPaid(true);
                        jdbcSession.updateById(order);
                        return dataUtil.getMessage(TencentPayResult.SUCCESS, "OK");
                    }
                    jdbcSession.updateById(order);
                    return dataUtil.getMessage(TencentPayResult.SUCCESS, "OK");
                }
                return dataUtil.getMessage(TencentPayResult.FAIL, "FAIL");
            } else {
                return dataUtil.getMessage(TencentPayResult.SUCCESS, "OK");
            }
        };

        String resultText = (String) Locks.exeSyn(orderSn, result);
        if (Objects.isEmpty(resultText)) {
            resultText = dataUtil.getMessage(TencentPayResult.FAIL, "FAIL");
            return Result.text(resultText);
        }
        // 默认失败，使用微信的通知次数
        return Result.text(result);
    }

    public Object unifiedQuery(Merchant merchant, Map<String, String> params) throws Exception {
        String subOrderNo = params.get("subOrderNo");
        String transaction_id = params.get("transaction_id");
        if (Objects.isEmpty(subOrderNo) && Objects.isEmpty(transaction_id)) {
            return Result.error(215111, "请输入必填参数！");
        }

        String nonce_str = params.get("nonce_str");
        if (Objects.isEmpty(nonce_str)){
            nonce_str = String.valueOf(ThreadLocalRandom.current().nextLong(Integer.MAX_VALUE, Long.MAX_VALUE));
            params.put("nonce_str", nonce_str);
        }

        if (Objects.isNull(merchant)) {
            return Result.error(215003, "商户参数不正确，请联系技术人员");
        }

        ThirdOrder order = getThirdOrder(merchant.getSubMchId(), null, subOrderNo, transaction_id);
        if (Objects.isNull(order)){
            return Result.error(215008, "订单不存在，请检查相关参数后重试");
        }

        params.remove("subOrderNo");
        params.put("appid", merchant.getAppId());
        params.put("mch_id", merchant.getMchId());
        params = WxPayUtil.buildSign(params, merchant.getMchSecret(), SignType.MD5);
        String toXml = PayUtil.toXml(params);
        JSONObject jsonObject = payConfig.postXML("/pay/orderquery", http -> http.setBody(toXml));
        if (TencentPayResult.SUCCESS.equipsAll(jsonObject.getString("return_code"))
            && TencentPayResult.SUCCESS.equipsAll(jsonObject.getString("result_code"))
            && TencentPayResult.SUCCESS.equipsAll(jsonObject.getString("trade_state"))
        ) {
            setPaidOrder(order, jsonObject);
            jdbcSession.updateById(order);
        }
        return Result.success(jsonObject);
    }

    public Object unifiedCreate(Merchant merchant, Map<String, String> params, String companyNo, String communityNo) throws Exception {
        String subOrderNo = params.get("subOrderNo");
        String nonce_str = params.get("nonce_str");
        if (Objects.isEmpty(nonce_str)){
            nonce_str = PayUtil.generateNonceStr();
            params.put("nonce_str", nonce_str);
        }

        String orderSn = insertOrderRecord(merchant, params, companyNo, communityNo, subOrderNo);
        if (Objects.isEmpty(orderSn)) {
            return Result.error(215008, "out_trade_no已存在，请更换后重试");
        }

        params.remove("payType");

        // if (Objects.nonEmpty(params.get("appid"))) {
        //     params.put("sub_app_id", params.get("appid"));
        // } else if (Objects.nonEmpty(merchant.getSubAppid())){
        //     params.put("sub_app_id", merchant.getSubAppid());
        // } else {
        //     // continue
        // }

        params.put("appid", merchant.getAppId());
        params.put("mch_id", merchant.getMchId());

        params.put("sub_mch_id", merchant.getSubMchId());
        params.put("out_trade_no", orderSn);

        params.put("notify_url", logicConfig.getUrl("/admin/payment/callback")); // 通知地址重置成我们的接口
        params = WxPayUtil.buildSign(params, merchant.getMchSecret(), SignType.MD5);
        String toXml = PayUtil.toXml(params);
        logger.info("###############微信创建订单请求参数：{}##############", toXml);
        JSONObject jsonObject = payConfig.postXML("/pay/unifiedorder", http -> http.setBody(toXml));

        logger.info("#######################微信创建订单回调本地解析后结果: {} ##########################", jsonObject.toJSONString());
        if (TencentPayResult.SUCCESS.equipsAll(jsonObject.getString("return_code"))) {
            String prepay_id = jsonObject.getString("prepay_id");
            String codeUrl = jsonObject.getString("code_url");
            long ts = TimeUtil.timeSec();

            String appId = Objects.nonEmpty(merchant.getSubAppid()) ? merchant.getSubAppid() : merchant.getAppId();
            // 支付参数必要参数
            JSONObject resultJson = new JSONObject();
            resultJson.put("appId", appId);
            resultJson.put("nonceStr", nonce_str);
            resultJson.put("package", "prepay_id=" + prepay_id);
            resultJson.put("signType", SignType.MD5);
            resultJson.put("timeStamp", ts);
            resultJson.put("paySign", getSign0(appId, merchant.getMchSecret(), nonce_str, prepay_id, ts));
            resultJson.put("codeUrl", codeUrl);
            // 支付必要参数结束

            // 业务参数
            resultJson.put("mchId", merchant.getMchId());
            resultJson.put("mchName", merchant.getMchName());
            resultJson.put("subMchId", merchant.getSubMchId());
            resultJson.put("subMchName", merchant.getSubMchName());
            resultJson.put("orderNo", subOrderNo);
            resultJson.put("prepay_id", prepay_id);
            logger.trace("#######################微信回调本地解析成功：{}##########################", resultJson.toJSONString());
            return Result.success(resultJson);
        }

        return Result.error(215002, jsonObject.getString("return_msg"));
    }

    public Object v2UnifiedCreate(Merchant merchant, Map<String, String> params, String companyNo, String communityNo) throws Exception {
        String subOrderNo = params.get("subOrderNo");
        String nonce_str = params.get("nonce_str");
        if (Objects.isEmpty(nonce_str)){
            nonce_str = PayUtil.generateNonceStr();
            params.put("nonce_str", nonce_str);
        }

        String orderSn = insertOrderRecord(merchant, params, companyNo, communityNo, subOrderNo);
        if (Objects.isEmpty(orderSn)) {
            return Result.error(215008, "out_trade_no已存在，请更换后重试");
        }

        params.remove("payType");
        params.remove("subOrderNo");

        // if (Objects.nonEmpty(params.get("appid"))) {
        //     params.put("sub_app_id", params.get("appid"));
        // } else if (Objects.nonEmpty(merchant.getSubAppid())){
        //     params.put("sub_app_id", merchant.getSubAppid());
        // } else {
        //     // continue
        // }

        params.put("appid", merchant.getAppId());
        params.put("mch_id", merchant.getMchId());

        params.put("sub_mch_id", merchant.getSubMchId());
        params.put("out_trade_no", orderSn);

        params.put("notify_url", logicConfig.getUrl("/admin/payment/v2/callback")); // 通知地址重置成我们的接口
        params = WxPayUtil.buildSign(params, merchant.getMchSecret(), SignType.MD5);
        String toXml = PayUtil.toXml(params);
        logger.info("###############微信创建订单请求参数：{}##############", toXml);
        JSONObject jsonObject = payConfig.postXML("/pay/unifiedorder", http -> http.setBody(toXml));

        logger.info("#######################v2UnifiedCreate微信创建订单回调本地解析后结果: {} ##########################", jsonObject.toJSONString());
        if (TencentPayResult.SUCCESS.equipsAll(jsonObject.getString("return_code"))) {
            String prepay_id = jsonObject.getString("prepay_id");
            String codeUrl = jsonObject.getString("code_url");
            long ts = TimeUtil.timeSec();

            String appId = Objects.nonEmpty(merchant.getSubAppid()) ? merchant.getSubAppid() : merchant.getAppId();
            // 支付参数必要参数
            JSONObject resultJson = new JSONObject();
            resultJson.put("appId", appId);
            resultJson.put("nonceStr", nonce_str);
            resultJson.put("package", "prepay_id=" + prepay_id);
            resultJson.put("signType", SignType.MD5);
            resultJson.put("timeStamp", ts);
            resultJson.put("paySign", getSign0(appId, merchant.getMchSecret(), nonce_str, prepay_id, ts));
            resultJson.put("codeUrl", codeUrl);
            // 支付必要参数结束

            // 业务参数
            resultJson.put("mchId", merchant.getMchId());
            resultJson.put("mchName", merchant.getMchName());
            resultJson.put("subMchId", merchant.getSubMchId());
            resultJson.put("subMchName", merchant.getSubMchName());
            resultJson.put("orderNo", subOrderNo);
            resultJson.put("platformOrderNo", orderSn);
            resultJson.put("prepay_id", prepay_id);
            logger.trace("#######################v2UnifiedCreate微信回调本地解析成功：{}##########################", resultJson.toJSONString());
            return Result.success(resultJson);
        }

        return Result.error(215002, jsonObject.getString("return_msg"));
    }

    private String insertOrderRecord(Merchant merchant, Map<String, String> params, String companyNo, String communityNo, String subOrderNo) {
        String openid = params.get("openid");
        String sub_openid = params.get("sub_openid");

        ThirdOrder thirdOrder = getThirdOrder(merchant.getSubMchId(), null, subOrderNo, null);
        if (Objects.nonNull(thirdOrder)) {
            return null;
        }

        String orderSn = IdGeneratorUtil.generate();
        // 创建订单
        thirdOrder = Pool.newInstance(ThirdOrder.class, orderSn);
        thirdOrder.setAppId(merchant.getAppId());
        thirdOrder.setSubAppId(merchant.getSubAppid());
        thirdOrder.setMchId(merchant.getMchId());
        thirdOrder.setSubMchId(merchant.getSubMchId());
        thirdOrder.setSubOrderNo(subOrderNo);
        thirdOrder.setPayType(PayTypeEnum.valueOf(params.get("payType")));
        thirdOrder.setOrderFee(Integer.parseInt(params.get("total_fee")));
        thirdOrder.setPaid(false);
        thirdOrder.setCallbackUrl(params.get("notify_url"));
        thirdOrder.setPayerUser(Objects.nonEmpty(openid) ? openid : sub_openid);
        thirdOrder.setIp(params.get("spbill_create_ip"));
        thirdOrder.setAttach(params.get("attach"));
        thirdOrder.setCompanyNo(companyNo);
        thirdOrder.setCommunityNo(communityNo);
        jdbcSession.insert(thirdOrder);
        return orderSn;
    }

    private ThirdOrder getThirdOrder(String subMchId, String orderNo, String subOrderNo, String outSn) {
        if (Objects.nonEmpty(orderNo)) {
            return Pool.get(ThirdOrder.class, orderNo);
        }
        FindOne<ThirdOrder> find = jdbcSession.findOne(ThirdOrder.class);

        if (Objects.nonEmpty(subMchId)) {
            find.eq("subMchId", subMchId);
        }

        if (Objects.nonEmpty(subOrderNo)) {
            find.eq("subOrderNo", subOrderNo);
        } else if (Objects.nonEmpty(outSn)) {
            find.eq("outSn", outSn);
        } else {
            return null;
        }

        return find.exe();
    }

    private String getSign0(String appId, String mchSecret, String nonce_str, String prepay_id, long ts) {
        StringBuffer signBuf = new StringBuffer();

        signBuf.append("appId=").append(appId).append("&");
        signBuf.append("nonceStr=").append(nonce_str).append("&");
        signBuf.append("package=prepay_id=").append(prepay_id).append("&");
        signBuf.append("signType=MD5").append("&");
        signBuf.append("timeStamp=").append(ts).append("&");
        signBuf.append("key=").append(mchSecret);

        return MD5.MD5Encode(signBuf.toString()).toLowerCase();
    }

    private void setPaidOrder(ThirdOrder order, JSONObject jsonObject) {
        order.setPaid(true);
        order.setOutSn(jsonObject.getString("transaction_id"));
        order.setPayTime(Java8TimeUtils.parseFromTimeEnd(jsonObject.getString("time_end")));
        order.setBankType(jsonObject.getString("bank_type"));
    }

    private void setV2PaidOrder(ThirdOrder order, JSONObject jsonObject) {
        order.setOutSn(jsonObject.getString("transaction_id"));
        order.setPayTime(Java8TimeUtils.parseFromTimeEnd(jsonObject.getString("time_end")));
        order.setBankType(jsonObject.getString("bank_type"));
    }
}
